package com.xuecheng.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.xuecheng.api.order.model.dto.OrdersDTO;
import com.xuecheng.api.order.model.vo.PayResultModel;
import com.xuecheng.api.order.model.vo.QueryOrderModel;
import com.xuecheng.api.search.model.dto.CoursePubIndexDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.page.PageRequestParams;
import com.xuecheng.common.domain.page.PageVO;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.content.CourseChargeEnum;
import com.xuecheng.common.enums.order.OrderDealStatusEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.JsonUtil;
import com.xuecheng.common.util.PaymentUtil;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.order.agent.SearchApiAgent;
import com.xuecheng.order.common.constant.OrderErrorCode;
import com.xuecheng.order.convert.OrderConvert;
import com.xuecheng.order.entity.Orders;
import com.xuecheng.order.entity.Pay;
import com.xuecheng.order.mapper.OrdersMapper;
import com.xuecheng.order.service.OrdersService;
import com.xuecheng.order.service.PayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 订单 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {

    @Autowired
    private SearchApiAgent searchApiAgent;


    /**
     * 1.判断关键数据
     * coursePubId  userName
     * 2.获得课程发布数据
     * 判断课程发布数据是否存在
     * 判断是否收费
     * 收费,创建订单数据
     * 不收费,不创建订单
     * 3.生成订单数据
     * 查询用户对该课程是否有订单数据
     * 有订单,修改订单
     * 判断订单交易状态
     * 初始化状态,修改课程最新价格
     * 其他状态,订单状态异常
     * 无订单,创建订单数据
     * 4.将订单数据转为dto返回
     */
    @Transactional
    public OrdersDTO createOrModifyOrder(Long coursePubId, String userName) {
        //1.判断关键数据
        if (ObjectUtils.isEmpty(coursePubId) || StringUtil.isEmpty(userName)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.获得课程发布数据
        RestResponse<CoursePubIndexDTO> response = searchApiAgent.getCoursePubIndexById4s(coursePubId);
        //判断课程发布数据是否存在
        if (!(response.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(response.getCode(), response.getMsg());
        }
        CoursePubIndexDTO coursePubIndexDTO = response.getResult();
        if (ObjectUtils.isEmpty(coursePubIndexDTO)) {
            ExceptionCast.cast(OrderErrorCode.E_160007);
        }
        //判断是否收费
        if (CourseChargeEnum.CHARGE_NO.getCode().equals(coursePubIndexDTO.getCharge())) {
            ExceptionCast.cast(OrderErrorCode.E_160003);
        }
        //3.生成订单数据
        //查询用户对该课程是否有订单数据
        LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Orders::getCoursePubId, coursePubId);
        queryWrapper.eq(Orders::getUserName, userName);

        Orders orders = this.getOne(queryWrapper);
        if (ObjectUtils.isEmpty(orders)) {
            //无订单,创建订单数据
            orders = new Orders();
            //主键
            orders.setId(null);
            //设置订单号:唯一
            orders.setOrderNo(PaymentUtil.genUniquePayOrderNo());
            //课程数据
            orders.setCoursePubId(coursePubId);
            orders.setCoursePubName(coursePubIndexDTO.getName());
            orders.setCompanyId(coursePubIndexDTO.getCompanyId());
            //用户数据
            orders.setUserName(userName);
            //订单数据(此功能比较简单，没有优惠券，只会考虑交易价格)
            orders.setPrice(coursePubIndexDTO.getPrice());
            orders.setStartTime(LocalDateTime.now());
            //一个新的订单会有一个订单的状态:初始态
            orders.setStatus(new Integer(OrderDealStatusEnum.ORDER_DEAL_INIT_STATUS.getCode()));

            boolean save = this.save(orders);
            if (!save) {
                ExceptionCast.cast(OrderErrorCode.E_160009);
            }
        } else {
            //有订单,修改订单
            //判断订单交易状态
            //其他状态,订单状态异常
            if (!(OrderDealStatusEnum.ORDER_DEAL_INIT_STATUS.getCode().equals(orders.getStatus().toString()))) {
                ExceptionCast.cast(OrderErrorCode.E_160006);
            }
            //初始化状态,修改课程最新价格
            LambdaUpdateWrapper<Orders> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(Orders::getPrice, coursePubIndexDTO.getPrice());
            updateWrapper.set(Orders::getChangeDate, LocalDateTime.now());
            updateWrapper.eq(Orders::getId, orders.getId());

            boolean update = this.update(updateWrapper);
            if (!update) {
                ExceptionCast.cast(OrderErrorCode.E_160009);
            }
            //查询修改后的最新数据
            orders = this.getById(orders.getId());
        }

        //4.将订单数据转为dto返回
        OrdersDTO ordersDTO = OrderConvert.INSTANCE.entity2dto(orders);
        return ordersDTO;
    }

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private PayService payService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Value("${order.pay.topic}")
    private String payTopic;

    /**
     * 业务分析：
     * PS:不管业务操作成功与否
     * 不能抛出业务异常，返回第三方所需要的结果内容
     * 1.解析第三方支付通知的结果数据
     * 获得支付成功的消息
     * 获得支付失败的消息
     * 2.获得支付结果数据并判断
     * Orderpay--订单支付数据
     * 2.1根据第三方返回订单编号获得订单数据Orders
     * 2.2根据orderId获得订单支付数据Orderpay
     * 判断订单数据支付状态
     * 判断订单支付数据支付状态  (只有订单没有支付,才会发送mq的事务消息)
     * 3.发送事务消息
     * 构建事务消息内容
     * 4.判断发送事务消息的结果
     * commit/Rollback
     * 5.返回第三方的支付通知结果
     */
    public String notifyPayResult(String xmlPayResult) {

        //1.解析第三方支付通知的结果数据
        WxPayOrderNotifyResult notifyResult = null;
        try {
            notifyResult = wxPayService.parseOrderNotifyResult(xmlPayResult);
        } catch (WxPayException e) {
            log.error("解析第三方支付数据失败: {}", e.getMessage());
            return WxPayNotifyResponse.fail(OrderErrorCode.E_160016.getDesc());
        }

        String returnCode = notifyResult.getReturnCode();
        if (Pay.WX_PAY_RESPONSE_SUCCESS.equals(returnCode)) {
            //获得支付成功的消息
            //获得订单号
            String orderNo = notifyResult.getOutTradeNo();
            //2.1根据第三方返回订单编号获得订单数据Orders
            LambdaQueryWrapper<Orders> ordersQueryWrapper = new LambdaQueryWrapper<>();
            ordersQueryWrapper.eq(Orders::getOrderNo, orderNo);
            Orders orders = this.getOne(ordersQueryWrapper);
            if (ObjectUtils.isEmpty(orders)) {
                return WxPayNotifyResponse.fail(OrderErrorCode.E_160008.getDesc());
            }
            //2.2根据orderId获得订单支付数据Orderpay
            Long ordersId = orders.getId();
            LambdaQueryWrapper<Pay> payQueryWrapper = new LambdaQueryWrapper<>();
            payQueryWrapper.eq(Pay::getOrderId, ordersId);

            Pay pay = payService.getOne(payQueryWrapper);
            if (ObjectUtils.isEmpty(pay)) {
                return WxPayNotifyResponse.fail(OrderErrorCode.E_160015.getDesc());
            }
            //判断订单数据支付状态
            //判断订单支付数据支付状态  (只有订单没有支付,才会发送mq的事务消息)
            if (!(OrderDealStatusEnum.ORDER_DEAL_INIT_STATUS.getCode().equals(orders.getStatus().toString()))) {
                return WxPayNotifyResponse.fail(OrderErrorCode.E_160006.getDesc());
            }
            if (!(Pay.IS_NOT_PAY.equals(pay.getStatus()))) {
                return WxPayNotifyResponse.fail(OrderErrorCode.E_160006.getDesc());
            }
            //3.发送事务消息
            //构建事务消息内容PayResultModel(包含：学成的订单、第三方支付系统的数据)
            PayResultModel resultModel = new PayResultModel();
            //订单数据
            OrdersDTO ordersDTO = OrderConvert.INSTANCE.entity2dto(orders);
            resultModel.setOrder(ordersDTO);
            //支付方式--第三方
            String payMethod = notifyResult.getTradeType();
            resultModel.setPayMethod(payMethod);
            //支付时间--第三方
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            String timeEnd = notifyResult.getTimeEnd();
            LocalDateTime payDate = LocalDateTime.parse(timeEnd, formatter);
            resultModel.setPayDate(payDate);
            //支付订单号--第三方
            String payNum = notifyResult.getTransactionId();
            resultModel.setPayNumber(payNum);
            //支付响应结果--第三方
            String payResp = notifyResult.getXmlString();
            resultModel.setPayResponse(payResp);
            //交易状态-第三方
            String tradeStatus = notifyResult.getResultCode();
            resultModel.setTradeStatus(tradeStatus);
            //设置金额
            //订单总金额
            Integer totalFee = notifyResult.getTotalFee();
            String totalAmount = BaseWxPayResult.fenToYuan(totalFee);
            resultModel.setTotalAmount(new BigDecimal(totalAmount));
            //应结订单金额(优惠券会设计到此数据，由于业务还不完善，无此值，所以对数据要判断是否为空)
            // 应结订单金额=订单金额-非充值代金券金额，应结订单金额<=订单金额
            Integer settlementTotalFee = notifyResult.getSettlementTotalFee();
            if (!(ObjectUtils.isEmpty(settlementTotalFee))) {
                String receiptAmount = BaseWxPayResult.fenToYuan(settlementTotalFee);
                resultModel.setReceiptAmount(new BigDecimal(receiptAmount));
            }
            //实际支付金额(优惠券会涉及到此数据，由于业务还不完善，无此值，所以对数据要判断是否为空)
            Integer cashFee = notifyResult.getCashFee();
            if (!(ObjectUtils.isEmpty(cashFee))) {
                String buyerPayAmount = BaseWxPayResult.fenToYuan(cashFee);
                resultModel.setBuyerPayAmount(new BigDecimal(buyerPayAmount));
            }

            //发送事务消息
            String jsonStr = JsonUtil.objectTojson(resultModel);
            Message<String> message = MessageBuilder.withPayload(jsonStr).build();

            TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction(payTopic, message, null);
            //4.判断发送事务消息的结果
            //commit/Rollback
            if (LocalTransactionState.COMMIT_MESSAGE != result.getLocalTransactionState()) {
                log.error("订单支付通知发送事务消息失败: {}");
                return WxPayNotifyResponse.fail(OrderErrorCode.E_160011.getDesc());
            }

        } else {
            //获得支付失败的消息
            return WxPayNotifyResponse.fail(notifyResult.getReturnMsg());
        }
        //5.返回第三方的支付通知结果
        return WxPayNotifyResponse.success("OK");
    }

    /**
     * 考虑到消息的幂等性（因为消息可能会出现重新发送）
     * 1.判断订单数据  是否存在
     * 订单状态 是否支付
     * 2.根据orderId获得订单支付数据并判断
     * 是否为空
     * 订单支付状态 是否支付
     * 3.修改订单及订单支付数据   第三方订单必须为支付成功，才能修改数据
     * 订单状态
     * 订单支付状态
     * 支付时间
     * 实收金额
     * 付款金额
     * 支付订单编号(第三方）
     * 支付响应结果(第三方）
     */
    @Transactional
    public void successPayment(PayResultModel resultModel) {

        //1.判断订单数据  是否存在
        OrdersDTO ordersDTO = resultModel.getOrder();
        Orders orders = this.getById(ordersDTO.getOrderId());
        if (ObjectUtils.isEmpty(orders)) {
            ExceptionCast.cast(OrderErrorCode.E_160008);
        }
        //订单状态 是否支付
        if (!(OrderDealStatusEnum.ORDER_DEAL_INIT_STATUS.getCode().equals(orders.getStatus().toString()))) {
            ExceptionCast.cast(OrderErrorCode.E_160006);
        }
        //2.根据orderId获得订单支付数据并判断
        LambdaQueryWrapper<Pay> payQueryWrapper = new LambdaQueryWrapper<>();
        payQueryWrapper.eq(Pay::getOrderId, orders.getId());

        Pay pay = payService.getOne(payQueryWrapper);
        //是否为空
        if (ObjectUtils.isEmpty(pay)) {
            ExceptionCast.cast(OrderErrorCode.E_160015);
        }
        //订单支付状态 是否支付
        if (!(Pay.IS_NOT_PAY.equals(pay.getStatus()))) {
            ExceptionCast.cast(OrderErrorCode.E_160006);
        }
        // 第三方订单必须为支付成功，才能修改数据
        if (Pay.WX_PAY_RESPONSE_SUCCESS.equals(resultModel.getTradeStatus())) {

            //3.修改订单数据及订单支付数据
            LambdaUpdateWrapper<Orders> ordersUpdateWrapper = new LambdaUpdateWrapper<>();
            //订单状态
            ordersUpdateWrapper.set(Orders::getStatus, new Integer(OrderDealStatusEnum.ORDER_DEAL_PAID_STATUS.getCode()));
            ordersUpdateWrapper.set(Orders::getEndTime, LocalDateTime.now());
            ordersUpdateWrapper.set(Orders::getChangeDate, LocalDateTime.now());
            ordersUpdateWrapper.eq(Orders::getId, orders.getId());

            boolean orderUpdate = this.update(ordersUpdateWrapper);
            if (!orderUpdate) {
                ExceptionCast.cast(OrderErrorCode.E_160013);
            }

            //订单支付状态
            pay.setStatus(Pay.IS_PAY);
            //支付时间
            pay.setPayDate(LocalDateTime.now());
            //实收金额
            pay.setReceiptAmount(resultModel.getReceiptAmount());
            //付款金额
            pay.setBuyerPayAmount(resultModel.getBuyerPayAmount());
            //支付订单编号(第三方）
            pay.setPayNumber(resultModel.getPayNumber());
            //支付响应结果(第三方）
            pay.setPayResponse(resultModel.getPayResponse());

            boolean payUpdate = payService.updateById(pay);
            if (!payUpdate) {
                ExceptionCast.cast(OrderErrorCode.E_160012);
            }
        } else {
            // 为了保证下游的监听器（消息的消费方）不会监听器支付失败的消息,需要抛出异常来让事务监听器返回Rollback
            ExceptionCast.cast(OrderErrorCode.E_160017);
        }
    }

    /**
     * 刷新支付状态
     *
     * @param orderNo
     * @return
     */
    @Override
    public Map<String, Object> refreshPayResult(String orderNo) {

        LambdaQueryWrapper<Orders> ordersQueryWrapper = new LambdaQueryWrapper<Orders>();

        ordersQueryWrapper.eq(Orders::getOrderNo, orderNo);

        Orders orders = this.getOne(ordersQueryWrapper);

        String status = orders.getStatus().toString();

        if (status.equals(OrderDealStatusEnum.ORDER_DEAL_PAID_STATUS.getCode())) {
            HashMap<String, Object> hashMap = new HashMap<>();
            hashMap.put("status", OrderDealStatusEnum.ORDER_DEAL_PAID_STATUS.getCode());
            return hashMap;
        }

        return null;

    }

    /**
     * 1.判断关键数据
     * userName  pageNo  pageSize
     * 2.创建查询条件
     * 3.创建分页条件
     * 4.查询并将po转为dto封装返回
     */
    public PageVO<OrdersDTO> queryOrderList(PageRequestParams params, QueryOrderModel model, String userName) {
        //1.判断关键数据
        if (StringUtil.isEmpty(userName)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        Long pageNo = params.getPageNo();
        Integer pageSize = params.getPageSize();
        if (pageNo < 1) {
            pageNo = PageRequestParams.DEFAULT_PAGE_NUM;
        }
        if (pageSize < 1) {
            pageSize = PageRequestParams.DEFAULT_PAGE_SIZE;
        }

        //2.创建查询条件
        LambdaQueryWrapper<Orders> ordersQueryWrapper = new LambdaQueryWrapper<>();
        ordersQueryWrapper.eq(Orders::getUserName, userName);

        if (!(ObjectUtils.isEmpty(model.getStatus()))) {
            ordersQueryWrapper.eq(Orders::getStatus, model.getStatus());
        }

        //根据订单时间倒叙排序
        ordersQueryWrapper.orderByDesc(Orders::getCreateDate);
        //3.创建分页条件
        Page<Orders> page = new Page<>(pageNo, pageSize);

        //4.查询并将po转为dto封装返回
        Page<Orders> pageResult = this.page(page, ordersQueryWrapper);
        List<Orders> ordersList = pageResult.getRecords();
        if (CollectionUtils.isEmpty(ordersList)) {
            return null;
        }

        //po-->dto
        List<OrdersDTO> ordersDTOS = OrderConvert.INSTANCE.entitys2dtos(ordersList);

        //封装
        PageVO<OrdersDTO> pageVO = new PageVO<>(ordersDTOS, pageResult.getTotal(), pageNo, pageSize);

        return pageVO;
    }

}
